home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-02 / multi2.zip / CPMULTI.DOC < prev    next >
Text File  |  1993-01-04  |  73KB  |  2,109 lines

  1. Dear Programmer,
  2.  
  3. thanks for using my Multi-Tasking Subsystem for Turbo Pascal 5.0.
  4. This product was designed to enable everyone to study programming in a
  5. multi-tasking environment, without having to spend a lot of money to
  6. purchase an operating system like OS/2 or XENIX (and of course the
  7. extension memory necessary to run it).
  8.  
  9. The Turbo Pascal Multi-Tasking Subsystem is a very inexpensive and
  10. yet powerful tool that adds multi-tasking capabilities to your
  11. Turbo Pascal 5.0 programs.
  12. You will be able to study the programming of parallel processes and
  13. problems of interprocess communication using the programming
  14. environment you are accustomed to.
  15.  
  16. Although the Multi-Tasking Subsystem initially was designed as a means
  17. of experimenting, it became very soon a powerful and easy to use
  18. product, which I hope you will like as much as I do.
  19.  
  20. At first, I would like to outline the capabilities of the basic system:
  21.  
  22. - up to 50 (increased on demand) independently executing
  23.   tasks in a single program
  24. - 3 priority levels
  25. - code sharing
  26. - preemptive scheduler, using a dynamic scheduling algorithm
  27. - you may use DOS-functions in your tasks safely. A task is never
  28.   interrupted within a DOS-function or a critical interrupt
  29. - size of timeslice and range of dynamic CPU-allocation programmable
  30. - message passing
  31. - semaphores
  32. - programmable timers
  33. - primitive event processing (up to 10 simultaneously active event
  34.   requests; increased on demand)
  35. - executes on any PC, XT, AT, PS/2 and full compatible running DOS 2.x
  36.   or 3.x
  37. - source code available to registered users
  38.  
  39.  
  40. Registered users will get an additional UNIT for FREE(!!) which is
  41. based upon the Multi-Tasking Subsystem and provides you with:
  42.  
  43. - extended keycodes (Chacter, Scan-Code, Shift-Statusword)
  44. - manipulation of the keyboard buffer (clear, add keycodes at the
  45.   end or in front of the actual buffer contents)
  46. - keyboard lock and unlock
  47. - execute a DOS-program as subtask
  48. - all you need to have tasks wait for a special hot-key that awakes
  49.   them
  50. - all you need to have tasks pop up over the DOS-program and suspend
  51.   the DOS-task while they are executing (another way of writing memory
  52.   resident programs)
  53.  
  54.  
  55. Well, no product is perfect!  If you would like to have a function the
  56. Multi-Tasking Subsystem does not include at present, contact me and
  57. perhaps you will find it in the next version. IF I decide to
  58. realize YOUR idea in a later version of the subsystem, you will
  59. receive a FREE-upgrade!  I hope you will understand that I reserve
  60. the right to decide which functions will be added.
  61.  
  62. In this connection, I would like to thank L. David Baldwin and
  63. TurboPower Software for their fabulous source-level debugger TDebug
  64. PLUS 4.01, that helped me very much during the development of my
  65. Multi-Tasking Subsystem.
  66.  
  67.  
  68. Let me stop here!  Please take the time to read carefully through the
  69. documentation before you "dive" into the world of multi-tasking.
  70.  
  71.  
  72. Best regards,
  73.  
  74.    Christian Philipps
  75.  
  76.  
  77.  
  78.  
  79.  
  80.  
  81.  
  82.                    Turbo  5.0  Multi-Tasking Subsystem
  83.  
  84.  
  85.                               User's Manual
  86.  
  87.  
  88.  
  89.  
  90.  
  91.  
  92.                            (c) Copyright 1988 by
  93.                          Christian Philipps, Moers
  94.                             all rights reserved
  95.  
  96.                        Version 1.30  / November 1988
  97.  
  98.  
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111.                      Author:   Christian Philipps
  112.                                Düsseldorfer Str. 316
  113.                                4130 Moers 1
  114.                                West-Germany
  115.  
  116.  
  117.  
  118.  
  119. Trademarks Mentioned
  120.  
  121. TurboPower Software is trademark of TurboPower Software, Scotts Valley
  122.  
  123. Turbo Professional is trademark of Sunny Hill Software, used under
  124. license to TurboPower Software
  125.  
  126. Turbo Pascal and TASM are registered trademarks of Borland International
  127.  
  128. MS-DOS, OS/2 and MASM are trademarks of Microsoft Corporation
  129.  
  130. A86 is trademark of Eric Isaacson Software, Bloomington
  131.  
  132. UNIX is trademark of AT & T
  133.  
  134. IBM PC, XT, AT, PS/2 and PC-DOS are trademarks of International
  135. Business Machines Corporation
  136.  
  137.  
  138.  
  139. License Agreement
  140.  
  141. The Turbo Pascal Multi-Tasking Subsystem is marketed through the
  142. ShareWare marketing concept.
  143.  
  144. Therefore I grant you the right to reproduce, distribute and use
  145. copies of this ShareWare version of our Multi-Tasking Subsystem
  146. (including the on disk documentation), on the express condition that
  147. you do not receive any payment, commercial benefit, other
  148. consideration for such reproduction or distribution, or change this
  149. license agreement or the copyright notice.
  150.  
  151. The Multi-Tasking Subsystem, the additional unit MTPOPUP and the
  152. documentation of both are copyright (c) 1988 by Christian Philipps,
  153. Düsseldorfer Str. 316, 4130 Moers 1, West-Germany.
  154.  
  155. You have the right to evaluate this ShareWare version for a period of
  156. 30 days to find out whether it suits your needs. If you decide to
  157. continue using this software, I expect that you become a registered
  158. user by sending DM 50 or $35 (object code license) / DM 100 or $70
  159. (object code + source code license) to
  160.  
  161.               Christian Philipps
  162.               Düsseldorfer Str. 316
  163.  
  164.               4130 Moers 1
  165.               West-Germany
  166.  
  167. As soon as I receive your registration, I will send you your
  168. personal copy of the subsystem plus a FREE unit based upon the basic
  169. subsystem (see introductory letter).
  170. Furthermore you will get technical support through mail, e-mail or
  171. phone during 1 year from the day of receipt.
  172.  
  173. Users purchasing an oject code license only may receive the source
  174. code of the latest version later at any point of time by sending
  175. another DM 50 or $35 to the adress above.
  176.  
  177.  
  178.  
  179. Disclaimer
  180.  
  181. I make no warranty of any kind, either express or implied, including
  182. but not limited to implied warranties of merchantability and fitness
  183. for a particular purpose, with respect to this software and the
  184. accompanying documentation.
  185.  
  186. In no case, I will be liable for any damages (including damages for
  187. loss of business profits, business interruption, loss of business
  188. information, or other pecuniary loss) arising out of the use of or
  189. inability to use this software, even if I have been advised of the
  190. possibility of such damages.
  191.  
  192.  
  193.  
  194. Getting Started
  195.  
  196. Throughout this documentation I assume that you are familiar with
  197. Turbo Pascal 5.0 in general and with the UNIT concept.
  198. I also assume that you know how to use the advanced features of Turbo
  199. Pascal 5.0, for example type-casting of pointers. If necessary,
  200. please refer to your Turbo Pascal documentation for more information
  201. on any of the compiler's internal functions or the functions contained
  202. in the units Crt, Dos,...
  203.  
  204.  
  205. The Multi-Tasking Subsystem mainly consists of four units:
  206.  
  207. a) CpMulti.TPU
  208.  
  209.    This is the basic Multi-Tasking Subsystem. All further units are
  210.    based upon this piece of software.
  211.  
  212. b) MTPOPUP.TPU (registered users only)
  213.  
  214.    MtPopUp is a unit, based upon the basic subsystem and providing
  215.    full control over the system keyboard and supports one DOS-program
  216.    to be run as a separate task. Tasks may be kept waiting for a
  217.    certain key-combination to be pressed and somewhat resident
  218.    programs can be written by executing another COMMAND.COM in the
  219.    foreground. Please note that this unit is available only to
  220.    registered users, who will receive it in return for their
  221.    registration.
  222.  
  223. c) QUEUE.TPU (Public-Domaine)
  224.  
  225.    This unit contains several functions, which help you to create,
  226.    delete and manage forward-chained list-structures. The structure
  227.    of a single list element is defined by your application and may be
  228.    any data structure. Passing data between separate tasks, you will
  229.    shortly find out that list structures, as provided by this unit,
  230.    are convenient for implementing pipe-like data structures.
  231.    Studying the routines contained in the accompanying source code for
  232.    QUEUE, you will learn how to assure the consistency of shared data
  233.    at any time during execution.
  234.  
  235. d) CPMISC.TPU
  236.  
  237.    Miscellaneous service routines, used by QUEUE. The source code of
  238.    this Unit is only partially made available.
  239.  
  240.  
  241. Let's take a look at the way to USE the Multi-Tasking Subsystem: The
  242. usage of this software is quite straightforward, provided you enable
  243. your compiler to find the necessary units (please refer to your
  244. compiler manual).
  245. You will incorporate the Multi-Tasking library in your programs by
  246. simply including CpMulti in the USES-statement. CpMulti must be the
  247. first unit of the subsystem preceded, however, by the basic units Crt
  248. (or alternatives) and Dos.
  249.  
  250. Example: USES Crt, Dos, CpMulti, MtPopUp, Queue;
  251.  
  252. The units CpMulti and MTPOPUP use Turbo's CRT-unit, so problems may
  253. arise, when using an alternative CRT-unit (such as the TurboProfes-
  254. sional unit TPCRT) in your program.
  255. For the special case that you own the fantastic TurboProfessional
  256. package of TurboPower Software, your diskette contains files with
  257. the extension .PRO, which I compiled using the TPCRT-unit. You only
  258. need to rename those files to the extension .TPU.
  259.  
  260. If you wish to use another alternative Crt-unit that conflicts with
  261. the standard CRT-unit, you will have to order the source code and
  262. recompile the subsystem.
  263.  
  264. After having set up your USES-statement properly, you will have to
  265. include the compiler switch S-, which prevents the compiler
  266. from generating object code that checks the stack boundaries. This
  267. is VERY important, because each task has its own private stack
  268. that is automatically reserved on the heap at the time of creation.
  269. Without the S- directive, you would get a stack overflow abort
  270. shortly after creating the first subtask.
  271.  
  272. Whenever a task-switch is performed, the Multi-Tasking Subsystem
  273. checks the private stack of a subtask A task which uses up its
  274. stackspace (minus a certain safety margin) is aborted. The subsystem
  275. issues an error message and marks the task as "Crashed". The memory
  276. occupied by its private stack is not freed and may be examined using a
  277. debugger.
  278.  
  279. Now let's have a look at the system startup. When your program comes
  280. to execution the initialisition code in CpMulti captures some
  281. interrupt vectors and sets up the task-table. It creates a
  282. null-process with lowest priority (a simple loop) that consumes
  283. CPU-time whenever no other process is currently computable. Finally
  284. the main program is started as task no. 2.
  285.  
  286. The shutdown again is fully automatic. When your program terminates
  287. or a runtime error brings you to a halt, CpMulti's exit procedure
  288. gains control, disables multi-tasking and restores the interrupt
  289. vectors previously captured.
  290. Only in rare occasions, your system will completely crash. This
  291. could happen, when one of your tasks runs mad and overwrites
  292. valuable system data.
  293.  
  294. You might start experimenting right now, BUT I recommend studying
  295. the rest of this documentation before. The following sections will
  296. give you a deeper understanding of the concept and the interior of the
  297. Multi-Tasking Subsystem.
  298. Multitasking is too complicated however, to go down to the last.
  299. Those of you, who would like do get a deeper insight into the design
  300. of multi-tasking operating systems should read the book "Operating
  301. Systems, Design and Implementation" by Andrew S. Tanenbaum, Prentice
  302. Hall or scan through the bookshelves of Prentice Hall or Adison
  303. Wesley. The book mentioned above helped me very much in designing the
  304. Multi-Tasking Subsystem and describes a UNIX-like operating system for
  305. personal computers in detail (MINIX + source code available from
  306. Prentice Hall).
  307.  
  308.  
  309. The Basic System (CpMulti)
  310.  
  311. 1. Introduction
  312.  
  313. At the moment, more and more programmers become interested in multi-
  314. tasking environments for personal computers. I suppose, anyone who
  315. intends to stay up to date, will have to deal with systems like OS/2
  316. or UNIX / XENIX /...
  317. Unfortunately, those systems are quite expensive and require lots of
  318. extension memory. Only few wealthy private programmers (have you
  319. ever seen one?) will be able to buy such an operating system already
  320. today.
  321.  
  322. At the time I started developing the Multi-Tasking Subsystem, my
  323. situation had been as described above. Now I'm in the position to
  324. provide you with an inexpensive means to study the design and
  325. synchronisation of parallel processes. In addition to that, the
  326. Multi-Tasking Subsystem will help you to write programs more complex
  327. and much more sophisticated, especially in areas like data
  328. communication, where quite a lot of actions are to be taken nearly
  329. simultaneously.
  330.  
  331.  
  332. Concept
  333.  
  334. Like UNIX or OS/2, the Multi-Tasking Subsystem was designed as a
  335. time-sharing system. It was not designed for real-time applications.
  336. A time-sharing system tries to divide the available computing power
  337. among all computable processes evenly. Therefore, a process that has
  338. used up its timeslice, is temporarily suspended and another
  339. computable process gains control (preemptive scheduling). This is
  340. accomplished by having a timer-interrupt-service-routine monitor the
  341. system activities about 18 times a second. This routine initiates a
  342. task-switch whenever the running task has used up its quantum.
  343. Although I could have used the AT-RTC (Real-Time Clock), which in fact
  344. would decrease the minimum possible timeslice, I have decided not to
  345. do so for several reasons:
  346.  
  347. a) The task-switch is the most time consuming action, the kernel has
  348.    to perform. A smaller timeslice, increases the number of possible
  349.    task-switches, but increases the kernel overhead as well.
  350.  
  351. b) The Multi-Tasking Subsystem could not run on PC or XT-computers,
  352.    because those do not have a real-time clock.
  353.  
  354. With a timer-interrupt occuring 18 times a second, the smallest
  355. possible timeslice is about 55,5 milliseconds. During the
  356. initialisation sequence the timslice is set to 110 milliseconds.
  357. If you choose a 55,5 millisecond timeslice the amount of computing
  358. power consumed by the mere task-switching process will be about 1,4
  359. percent (12 MHz AT-compatible).
  360.  
  361.  
  362. Dynamic Scheduling
  363.  
  364. Normally each task is temporarily suspended whenever it has used up
  365. its timeslice. If this happes inside a DOS-function, however, the
  366. kernel may not safely interrupt the currently executing task. It has
  367. to wait, until it returns from the DOS-call.
  368. Provided this task has to read thousands of records from a data file
  369. and write them to another file, the kernel will be inside DOS
  370. again, when the kernel tries to suspend it at the following
  371. timer-tick. Again, a task switch is impossible! This procedure might
  372. happen again and again until the task has finished.
  373. As you see, under certain circumstances, a task may exceed its
  374. quantum at an amount that cannot be foreseen by the system-kernel.
  375.  
  376. On the other hand, there might be a task that periodically, lets say
  377. one time a second, look at a particular value taken from a port to
  378. see, whether actions are to be taken. In this case, it will act upon
  379. the condition encountered, otherwise it will go to sleep and give up
  380. its timeslice. Such a task will use up its timeslice very seldom.
  381. If, hovewer, actions are to be taken, it might be suspended in the
  382. middle of its activities because it has used up its quantum. - Indeed
  383. that's not fair!
  384.  
  385. What can we do to achieve some kind of compensation? OS/2 brought me
  386. to the idea to implement a somewhat dynamic scheduling that proceeds
  387. as follows: Whenever a task gives up its timeslice, the time not used
  388. is placed to this task's credit. If it has to execute longer at a
  389. later point of time, it will not be preempted until it has used up its
  390. current quantum plus its credit. On the other hand, a task that
  391. exceeds its quantum, is not rescheduled until its debit is "used up".
  392.  
  393. This sounds quite good, but there is a catch to it though. If we
  394. would credit every single tick not used, the task mentioned above
  395. could possibly run for hours if it has to take some action after
  396. all.
  397. For this reason, the amount of ticks beeing placed to a tasks
  398. credit/debit, is limited. You may choose this limit freely and it
  399. defaults to +/- 10 timer-ticks that is slightly above half of a
  400. second.
  401.  
  402.  
  403. Priority
  404.  
  405. The Multi-Tasking Subsystem supports three levels of priority:
  406. "Pri_Nice" which is the lowest, "Pri_User" which is standard and
  407. "Pri_Kernel" which is highest possible priority. Your Pascal
  408. main-program is started at the level "Pri_User" during initialization.
  409.  
  410. Let's examine, how the different layers of priority are acted upon:
  411. Inside each layer, the tasks are scheduled in straight round-robin
  412. fashion. Whenever there are computable tasks at a higher level none
  413. of the tasks in a level below that will gain control. That means, the
  414. "idle loop" at level "Pri_Nice" will only be activated, if the
  415. layers "Pri_User" and "Pri_Kernel" are empty.
  416.  
  417. The programmer has to take this concept into account when designing
  418. his application system. The overall system performance depends very
  419. much on the assignment of priorities.  Any task can change its
  420. priority at runtime by means of a special system service.
  421.  
  422. By the way: Throughout this documentation I use the terms "task" and
  423. "process" interchangably, although some experts will be picking up
  424. stones to throw at me at this very moment...  I don't feel guilty
  425. doing so, because those experts seem not to have reached an agreement
  426. yet, about how to define these terms.
  427.  
  428. Tasks that spend most of their lives waiting for events (waiting for
  429. incoming characters,...), should be given highest priority.
  430. Tasks that spend their lives busily working all the time, should be
  431. given standard pritority.
  432. By doing so, the busy ones will be interrupted whenever an event, a
  433. higher priorized task was waiting for occurs and will be able to work
  434. the rest of the time (which will be most of the time) undisturbed.
  435. The lowest priority level will mostly be occupied only by the idle
  436. loop.
  437.  
  438.  
  439. Another Catch To It
  440.  
  441. DOS does not know about interrupt controlled disk I/O in the
  442. fashion of multi-tasking operating systems! There is a loop inside
  443. your ROM that waits for the disk-controller to issue a completion
  444. interrupt, but that is of no use for us, because a disk I/O
  445. operation must never be interrupted by a task-switch.
  446. Multi-tasking operating systems normally switch to another task,
  447. while the currently running process has to wait for a disk operation
  448. to complete. I didn't know how to achieve this without rewriting
  449. the low-level disk I/O-routines. So for now, we'll wait...
  450.  
  451. As a consequence, a process that performs lots of disk I/O-operations
  452. and which is running at high priority, will possibly block the whole
  453. system.
  454. In a real multi-tasking operating system however, it is very useful
  455. running I/O-intensive processes at a high priority level for the
  456. reasons stated earlier in the context of mostly waiting tasks.
  457.  
  458.  
  459. Inter Process Communication (IPC)
  460.  
  461. The Multi-Tasking Subsystem contains functions for handling
  462. semaphores and message-passing.
  463. Shared-memory does not require system support, because every task
  464. under DOS has access to any memory location in the whole system.
  465. The programmer will have to use semaphores, however, to avoid race
  466. conditions when accessing commonly used memory areas. The
  467. demonstration program PRO_CON gives examples of how to synchronize
  468. your tasks using semaphores.
  469.  
  470.  
  471. Programmable Timers
  472.  
  473. Programmable timers are a very useful means to take care of timeout-
  474. conditions (for example in communications software).
  475. The Multi-Tasking Subsystem let you define any number (limited by
  476. the available heap) of timers with a resolution of 55,5
  477. milliseconds. If a timer expires, a byte located at the address given
  478. at the time of creation is incremented.
  479. The amount of CPU-time consumed by watching the timers currently
  480. active is negligible and independent of the number of timers defined.
  481.  
  482.  
  483. Event Handling
  484.  
  485. Sometimes it can be useful to have a task waiting for the contents of
  486. a memory location to change. This is what I have chosen to call an
  487. event. Realizing a task that waits for a buffer filled by an
  488. interrupt routine, you can acchieve a very short response time at a
  489. minimum waste of CPU-time by defining an event that reactivates your
  490. task whenever the contents of the buffer's tail-pointer changes.
  491.  
  492. Starting with release 1.30, the subsystem kernel puts a task,
  493. awakened by an event occuring, at the head of the task-queue
  494. corresponding to its run level. Thus it is put in the position of
  495. beeing able to react on the event very quickly.
  496.  
  497.  
  498.  
  499. 3. How Is It Done
  500.  
  501. Most of the subsystem is written in Turbo Pascal 5.0. Only a small
  502. part of the kernel (about 2 KB) is written in assembly language. If
  503. you purchased a source code license, you will find two versions of
  504. the assembly language module on your product diskette. The first one
  505. assembles using Microsofts Macro Assembler V4.0 or higher or TASM 1.0,
  506. the second one is for programmers using the well known ShareWare
  507. assembler A86 V3.19 or higher, written by Eric Isaacson.  Both parts
  508. together form a TPU (Turbo Pascal Unit) that contains the whole
  509. subsystem and may be USEd in your programs.
  510.  
  511.  
  512. The Task Table
  513.  
  514. The central element of the Multi-Tasking Subsystem is called "task
  515. table". For reasons of performance, this table was defined
  516. statically in the data segment. It can hold a maximum of 50 tasks at
  517. the moment, but can easily be expanded (if you own the source code).
  518.  
  519. Every task in the system occupies one entry in the task table. This
  520. entry holds all pieces of information that describe the task's actual
  521. state of execution.
  522.  
  523. At any point of time each task is in one of the following possible
  524. states:
  525.  
  526. Running..: This task is currently executed.
  527.  
  528. Ready....: This task is computable, but another task currently is in
  529.            control of the CPU.
  530.  
  531. Sleeping.: This task has suspended itself for a certain number of
  532.            timer-ticks.
  533.  
  534. Crashed..: The subsystem has detected a stack overflow for this task.
  535.  
  536. Waiting..: This task is currently waiting for an event or a
  537.            resource (a semaphore for example).
  538.  
  539. Sending..: This task is waiting for the receiver of a message to
  540.            do a receive system call.
  541.  
  542. Receiving: This task is waiting for another task to send it a message.
  543.  
  544. Suspended: This task was temporarily disabled by another task in the
  545.            system and needs action from another task to be put into
  546.            the ready-state again.
  547.  
  548. If a slot in the task-table currently is not occupied by any task its
  549. state-marker will contain the value "Available".
  550.  
  551. The following state diagram summarizes all possible task-states and
  552. the state-transitions possible among these states. A state-transition
  553. may only take place in the direction indicated by the arrow symbols.
  554.  
  555.  
  556.  
  557.  
  558.  
  559.  
  560.                        ┌───────────┐            13 ┌───────────┐
  561.                ┌──────>│  Running  ├───────┬──────>│  Crashed  │
  562.                │       └─────┬─────┘       │       └───────────┘
  563.                │             │             │
  564.               2│            1│             │
  565.                │             │             └────────────┐
  566.                │             │                          │
  567.          ┌─────┴─────┐       │     4 ┌───────────┐      │
  568.          │   Ready   │<──────┴─────┬─┤ Waiting   │<─┐3  │
  569.          └───────────┘             │ ├───────────┤  │   │
  570.                                   6├─┤ Sleeping  │<─┤5  │
  571.                                    │ ├───────────┤  │7  │
  572.                                   8├─┤ Sending   │<─┼───┘
  573.                                    │ ├───────────┤  │
  574.                                  10├─┤ Receiving │<─┤9
  575.                                    │ ├───────────┤  │
  576.                                  12└─┤ Suspended │<─┘11
  577.                                      └───────────┘
  578.  
  579.  
  580.  
  581.  
  582.             State Diagram, Multi-Tasking Subsystem Version 1.10
  583.  
  584.  
  585. Task Management
  586.  
  587. Internally, the tasks are managed using queues. Dependent on its
  588. state of execution, a task may be located in
  589.  
  590. a) the scheduler queue of its priority-level ("Running" or "Ready"),
  591. b) the sleep-queue ("Sleeping") or
  592. c) the sender-queue of the receiver-task, if the latter one is not
  593.    ready to receive the message and the sender indicates that it
  594.    wants to wait until the receiver gets ready ("Sending").
  595.  
  596. Tasks at the states "Suspended", "Receiving" or "Crashed" are
  597. not located in any queue.
  598. A task that is "Waiting" on an event or a resource may be chained to
  599. the event-table or be located in a semaphore's task-queue.
  600.  
  601. Whenever a task is created, a task-table entry is allocated to
  602. this task. The slot number of its task-table entry becomes the
  603. task-id (task-number or process-number), which some system-calls
  604. are to be passed as a parameter.
  605. In addition to that, a private stack for the task is allocated and
  606. set up.
  607.  
  608. The task creation can be completed sucessfully only if a) there is
  609. an empty slot in the task-table and b) the private stack can be
  610. allocated on the heap.
  611.  
  612.  
  613.  
  614. Code-Sharing
  615.  
  616. The Turbo Pascal compiler generates object code that is mostly
  617. reentrant. Such code may be executed in code-sharing.
  618.  
  619. The term "Code-sharing" can have different meanings. On the one
  620. hand there may be a number of subroutines which are used by more
  621. than one task in the system. Thus more than one task may be
  622. executing a certain subroutine at the "same" point of time, i. e.
  623. the instruction pointers of those tasks point to a location inside
  624. this subroutine at the same point of time.
  625. Provided the code is reentrant, this will never lead to any
  626. problems. You will have to take care of situations which lead to
  627. more than one task modify global data at the same point of time, of
  628. course, but the type of code-sharing described above will never lead to
  629. a crash or the modification of local variables.
  630.  
  631. "Code-sharing" may also describe a situation, in which the same part
  632. of code (here: the object code of a procedure - the task) is activated
  633. more than once as a separate process. Each of these tasks is given
  634. its own set of registers, its private stack and its local variables.
  635. The latter are actually allocated on the private stack. Provided the
  636. code is reentrant and provided that you, the programmer, take care of
  637. race conditions, there may be an "unlimited" number of separate tasks
  638. executing the same object code simultaneously.
  639.  
  640. I have already tried both kinds of code-sharing and did not encounter
  641. any problems so far.
  642.  
  643.  
  644. Programmable Timers
  645.  
  646. Lets now have a closer look at the timers. A timer, which in fact is
  647. a memory location containing a counter beeing decremented at any
  648. timer-tick, needs to be examined whenever a clock-interrupt occurs.
  649. If the kernel realizes that the timer has expired, some action will
  650. be to be taken.
  651. As you see, a small extra amount of computing power is necessary to
  652. countdown timers, act upon their expiration and finally remove them
  653. from the system. Some types of application, communication programs
  654. for example require extensive use of timers. Therefore one should
  655. think about an inexpensive implementation of timers to avoid loss of
  656. performance.
  657. The implementation of timers in the Multi-Tasking Subsystem makes the
  658. kernel overhead constant and independant from the number of timers
  659. presently active. This is achieved by a special queueing method,
  660. taken from the timer implementation of MINIX, a UNIX-like operating
  661. system for the IBM PC. As stated earlier, MINIX has been developed by
  662. Andrew S. Tanenbaum and is currently distributed by Prentice Hall. I
  663. recommend MINIX for everybody, who would like to go deeper into
  664. operating system design, it's fantastic!
  665.  
  666. Return to the timers: The timer-queue consists of a forward-chained
  667. list of timer-structures. Only the first element of this list is
  668. examined at every timer-tick. If the first timer expires, it will be
  669. removed from the queue after having taken the appropriate action to
  670. signal its expiration to the application. The second element now
  671. becomes the head of the queue, and so on.
  672. When a new timer is inserted into the timer-queue, its counter will be
  673. adjusted to reflect the number of ticks remaining after all previous
  674. elements of the timer-queue have expired.
  675.  
  676. Example:
  677.                 ┌─────┬─┐    ┌─────┬─┐    ┌─────┬─┐
  678. Timer-Queue ───>│  3  │─┼───>│  5  │─┼───>│  1  │─┼─┤
  679.                 └─────┴─┘    └─────┴─┘    └─────┴─┘
  680. Time to wait:      3 Ticks      8 Ticks      9 Ticks
  681.  
  682.  
  683. Event Handling
  684.  
  685. You might think of lots of events that could be handled by an
  686. application.
  687. Currently only the change of contents with respect to a single
  688. memory location is supported. Practically speaking, your application
  689. may wait (without consuming CPU) until the contents of a particular
  690. memory location changes. This memory location is checked whenever a
  691. task-switch occurs.
  692.  
  693. What to do with it?  Well, I came up with this type of event as I
  694. designed a special application. I had to realize a task that emptied
  695. a ring-buffer which was filled by an interrupt routine. Why not use a
  696. semaphore, you might ask!  - Too slow!  There was just enough time to
  697. insert the characters received into the ring-buffer. On the other
  698. hand the ring-buffer had to be emptied as quickly as possible, again
  699. for the reasons of speed.
  700. A high-priorized subtask permanently looping, keeping an eagle eye on
  701. the ring-buffer is far from beeing a solution - it simply blocks the
  702. whole system. OK, lets try to have the subtask sleep for a while,
  703. take a look at the buffer and sleep again if no characters have
  704. arrived. Well, in the meantime a buffer-overflow has occured...
  705.  
  706. I suppose, you now understand why the event has been born. Defining
  707. an event that monitored the buffer's tail-pointer, I kept my
  708. high-priority subtask waiting for the buffer to be filled without
  709. blocking the whole system. Awakend very shortly after a change of the
  710. tail-pointer signalizes incoming characters, it busily processed the
  711. buffer contents and went to sleep again. - Works fine!
  712.  
  713.  
  714. Inter Process Communication (IPC)
  715.  
  716. As mentioned earlier, the Multi-Tasking Subsystem provides for
  717. various methods to exchange data between separate tasks and to
  718. synchronize processes executing simultaneously.
  719. I do not intend to go into detail about the underlying concepts,
  720. because there are lots of books available in the market, which discuss
  721. these topics much better than I could do here in this document. Again
  722. I recommend the study of "Operating Systems Design and Implementation"
  723. written by Andrew S. Tanenbaum.
  724.  
  725.  
  726. A. Message Passing
  727.  
  728. The system-calls supporting message passing are quite
  729. straightforward.
  730. A task that wants to send a message to another task in the system
  731. primarily has to know the task-id of the process it would like to
  732. send to. Then it passes its message-buffer and the receiver-id to
  733. the appropriate system-call. An additional parameter tells the
  734. kernel, whether the sender wants to wait if the receiver is not
  735. ready to receive the message or whether the send-request should be
  736. rejected.
  737.  
  738. A task is ready to receive, whenever it executes a receive-system-
  739. call. It may specify the task-id of the process it wants to receive
  740. from or pass a don't-care code to the system-call, which causes the
  741. kernel to pass through any message sent to this task.
  742. Again, the task may wait until a message arrives or have the system
  743. reject a receive system-call whenever no message is available.
  744.  
  745.  
  746. B. Semaphores
  747.  
  748. There has been written a lot about this topic in the past. Let's have
  749. a look at the internal represetation only at this point.
  750.  
  751. A semaphore as defined by the Multi-Tasking Subsystem, consists of a
  752. two-byte signal count, which is set to 1 at the time of creation,
  753. and a task-queue to which the processes waiting for this semaphore
  754. are appended.
  755. The busy-state is indicated by a signal-count of zero; any non-zero
  756. value indicates a free-state. Whenever a wait system-call is executed
  757. and the corresponding semaphore is currently busy, the requesting
  758. task is suspended until another process releases this semaphore
  759. through execution of a signal system-call.
  760. Whenever a signal system-call is executed and the task-queue of the
  761. corresponding semaphore is not empty, the first task of this queue
  762. is made computable, otherwise the signal-count is incremented.
  763.  
  764. Despite the internal structure, tasks refer to semaphores through
  765. untyped pointers. The task creating a semaphore receives this
  766. pointer on return from the system-call and uses it as a handle
  767. furtheron. The actual number of semaphores in the system is
  768. delimited by the amount of free heap space only.
  769.  
  770.  
  771. Critical Sections
  772.  
  773. You may use the standard functions/procedures supplied with your
  774. compiler freely inside your tasks. BUT - procedures like WriteLn are
  775. not designed to work in multi-tasking environments, that means, they do
  776. not protect their critical sections from beeing entered more than once
  777. at a time.
  778. An example will help to understand what I try to explain: Lets think
  779. of two equaly priorized tasks which spend their lives WriteLn-ing
  780. strings to the terminal screen. The process of bringing a bunch of
  781. characters to the screen is much more complicated than the simple
  782. WriteLn-statement makes believe. Therefore it is likely that
  783. sometimes both tasks will be preempted while in the middle of
  784. performing the screen-write. As a consequence the screen output
  785. becomes clobbered.
  786. Imagine the effects of non-synchronized execution of GetMem and
  787. FreeMem...
  788.  
  789. Another situation in which you have to block a certain section of code
  790. is a routine, that modifies global data or performs some closely
  791. connected actions that form an undivisible unity (atomic action).
  792. Let me give you an example: Provided you got two tasks executing
  793. simultaneously, which at some point of execution output a character to
  794. the screen at a certain position. This is done by executing a GotoXY,
  795. followed by a Write. If the first task was interrupted after having
  796. executed the GotoXY but before having output the character, the second
  797. task might move the cursor to another location on the screen, which
  798. indeed wouldn't lead to the results desired.
  799.  
  800. There are two basically different approaches to solve this problem.
  801. First you could force exclusive CPU-access during the execution of
  802. your critical section. This solution is only applicable for very
  803. small portions of code, a GotoXY/Write-sequence for example, because
  804. a CPU-bind blocks any scheduler function including the sleep-queue
  805. and timer management.
  806. In most cases a protection scheme using semaphores is preferable.
  807. You can find an example for this kind of process synchronisation in
  808. the example program PRO_CON.PAS, routines RBuffGet and RBuffPut. A
  809. section of code protected by a semaphore, may only be entered by one
  810. task at a time. Any task trying to enter the critcal section whilst
  811. another process is currently inside is suspended until the latter one
  812. has left the protected area.
  813.  
  814.  
  815.  
  816. Global Type- and Data-Definitions
  817.  
  818.  
  819. Global Type-Definitions
  820.  
  821. Priority = (Pri_Nice, Pri_User, Pri_Kernel);
  822.  
  823.   This type-definition is used to describe the priority-level at
  824.   which a task is to be executed.
  825.  
  826.  
  827. WaitFlagType = (Wait, NoWait);
  828.  
  829.   Passed as a parameter to the system-calls concerned with
  830.   message-passing, this type indicates whether a process intends to
  831.   wait for a) a message to be received or b) a message available to
  832.   be received.
  833.  
  834.  
  835. TaskReturn = (Task_Crashed, Task_NotFound, Task_NoMsg,
  836.               Task_NotReady, Task_OK, Task_Invalid);
  837.  
  838.   The system-calls concerned with message-passing return one of these
  839.   codes to the calling process.
  840.  
  841.   Task_Crashed...: A task was addressed, which has been disabled
  842.                    following a stack-overflow condition
  843.   Task_NotFound..: The task-table entry indexed by the task-id
  844.                    passed to the executed system-function is
  845.                    out of range or currently empty
  846.   Task_NoMsg.....: There is no message available (Receive with NoWait)
  847.   Task_NotReady..: The receiver is not waiting for a message (Send
  848.                    with NoWait)
  849.   Task_OK........: Fine, that's it
  850.   Task_Invalid...: An invalid task-id was passed to the
  851.                    system-function
  852.  
  853.  
  854. TaskStatus = (Running, Ready, Sleeping, Available, Crashed,
  855.               Waiting, Sending, Receiving, Suspended);
  856.  
  857.   Return value returned by GetTaskState.
  858.  
  859.  
  860. SemReturn = (Sem_NoSpace, Sem_NotFree, Sem_OK, Sem_Invalid);
  861.  
  862.   Some system-calls concerned with semaphore-handling return a value
  863.   of type SemReturn.
  864.  
  865.   Sem_NoSpace..: There is no heap space available
  866.   Sem_NotFree..: You have tried to delete a semaphore whose task-queue
  867.                  is not empty
  868.   Sem_OK.......: Everything's allright
  869.   Sem_Invalid..: You passed an invalid semaphore-handle (NIL)
  870.  
  871.  
  872. TaskNoType = Integer;
  873.  
  874.   Higher level representation of the task-id. I decided to redefine
  875.   the type integer to be able to change this definition in a later
  876.   version without having to think about the program code.
  877.  
  878.  
  879. BPtr = ^Boolean;
  880.  
  881.   Definition required by GetTimBusy.
  882.  
  883.  
  884.  
  885. Global Constants
  886.  
  887. Task_NoSpace
  888.  
  889.   If task creation fails, because there is not enough free dynamic
  890.   memory available to allocate a private stack, CreateTask will return
  891.   this value instead of the task-id.
  892.  
  893.  
  894. Task_NoSlot
  895.  
  896.   If task creation fails, because the maximum number of simultaneously
  897.   active tasks is reached, CreateTask will return this value instead
  898.   of the task-id.
  899.  
  900.  
  901. StateText
  902.  
  903.   StateText is an array indexed by a value of type TaskStatus, which
  904.   contains a textual representation of every possible task state.
  905.  
  906.  
  907. AnyTask
  908.  
  909.   Don't-care-value, passed to the receive system-function whenever a
  910.   process doesn't care from whom it is going to receive a message.
  911.  
  912.  
  913. Global Variables
  914.  
  915. InInt28 (Boolean)
  916.  
  917.   Whenever COMMAND.COM is waiting for input, it permanently issues
  918.   an INT 28H (DOS multi-tasking interrupt). MtPopUp for example
  919.   catches this interrupt and sets InInt28 to TRUE whilst inside the
  920.   interrupt-handler. This informs the kernel know that a task-switch
  921.   may safely performed although the DOS critical-flag is set.
  922.  
  923.  
  924.  
  925. Internal Type- and Data-Definitions
  926.  
  927. Internal Type-Definitions
  928.  
  929. CSDataType = RECORD
  930.                TimBusy : Boolean;
  931.                Old08   : Pointer;
  932.                ISRStat : Byte;
  933.              END;
  934.  
  935.   CSDataType describes a small status-area located in the code
  936.   segment of the assembly language module.
  937.   "TimBusy", the timer-busy flag, is set to a non-zero value
  938.   whenever the interrupt-service routine handling timer-tick
  939.   interrupts is currently executing. This prevents reentrance, not
  940.   allowed in this case.
  941.   If you block the CPU by executing BindCPU, this flag is also set
  942.   to simulate a timer-busy condition.
  943.   "Old08" contains the interrupt-vector pointing to the original
  944.   interrupt-handling procedure.
  945.   There are various interrupt-handlers that must not be disturbed by a
  946.   task-switch (for example the disk-I/O vector 13H). The byte
  947.   addressed by "ISRStat" is used to store a number of interrupt-busy
  948.   flags, one for each interrupt that may not be interrupted. By
  949.   entering the corresponding interrupt-handling procedure, the flag is
  950.   set to 1 and reset on return. At task-switch may only be performed,
  951.   if both, the critical-flags and the ISRStat, contain zero.
  952.  
  953.  
  954. TaskPtrType = ^TaskDescType;
  955.  
  956.   Pointer to a task-table slot.
  957.  
  958.  
  959. SchedRecType = RECORD
  960.                  First : TaskPtrType;
  961.                  Last  : TaskPtrType;
  962.                END;
  963.  
  964.   The anchor of a task-queue. Such task-queues are represented
  965.   internally as forward-chained list structures. The scheduler-queues
  966.   for example are task-queues that contain all tasks in the system
  967.   that are computable or running at the moment. The first element of
  968.   the queue is addressed using the pointer "First", the tail of the
  969.   queue may be found by following the pointer "Last". This enables
  970.   the kernel to quickly append new elements to the end of the queue.
  971.  
  972.  
  973. TaskDescType = RECORD
  974.                  NextTask  : TaskPtrType;
  975.                  TaskNo    : TaskNoType;
  976.                  SaveArea  : SaveAreaType;
  977.                  Status    : TaskStatus;
  978.                  RunLevel  : Priority;
  979.                  Stack     : PointerType;
  980.                  StackSize : Word;
  981.                  CPU       : LongInt;
  982.                  TimeSlice : Integer;
  983.                  DelayCount: LongInt;
  984.                  Senders   : SchedRecType;
  985.                  RecFrom   : TaskNoType;
  986.                  RecBuff   : Pointer;
  987.                END;
  988.  
  989.   Here you see the structure of a so-called task-descriptor, which
  990.   makes up one slot of the task-table.
  991.   "NextTask" is used to chain the tasks in a queue together. The
  992.   queue is terminated by a NIL value contained in the NextTask field.
  993.   The task-id is stored in the field named "TaskNo". This value is
  994.   identical to the slot-number of the corresponding task-descriptor.
  995.   Quite a number of system-calls need this value to index into the
  996.   task-table when manipulating the contents of a particular task-
  997.   descriptor.
  998.   As you already know, every task possesses a private stack. The
  999.   actual top of stack as represented by the registers SS and SP, is
  1000.   stored in the structure named "SaveArea". The actual size and
  1001.   location of this stack are stored in fields named "Stack" and
  1002.   "StackSize". The kernel checks for a stack-overflow condition
  1003.   whenever a task-switch is performed.
  1004.   "Status" contains the acutal state of a task. If this field
  1005.   contains the value "Available", the task-table slot is empty and
  1006.   may be assigned to a newly created task.
  1007.   The field indicating a task's priority is named "RunLevel". The
  1008.   contents of this field is used to select the appropriate
  1009.   scheduler-queue.
  1010.   "TimeSlice" is used to keep the number of timer-ticks already used
  1011.   up during the current timeslice. This value might become negative
  1012.   due to dynamic scheduling.
  1013.   The last field concerned with time-sharing is named "DelayCount".
  1014.   Whenever a task goes to sleep for a while, this field is filled
  1015.   with the number of ticks the task intends to stay asleep. It is
  1016.   decremented on every timer-tick until the value zero is reached.
  1017.   Than the task is re-scheduled.
  1018.   The remaining fields in the task-descriptor are concerned with
  1019.   message-passing. "Senders" is the anchor of a task-queue
  1020.   containing the tasks wishing to send to this process.
  1021.   "RecFrom" is used to indicate which task a process wants to
  1022.   receive from on the one hand and to hold the task-id of the sender
  1023.   after having successfully received a message on the other hand.
  1024.   The latter function is important when receiving messages using the
  1025.   don't care code "AnyTask". The receiver will not know in advance
  1026.   from whom he will receive a message. He may find it out by
  1027.   questioning the contents of "RecFrom".
  1028.   "RecBuff" points to the message-buffer.
  1029.  
  1030.  
  1031. Semaphore     = RECORD
  1032.                   Signals   : Word;
  1033.                   WaitQueue : SchedRecType;
  1034.                 END;
  1035.  
  1036.   Here we have the internal representation of a semaphore. The
  1037.   signal-count is stored in "Signals" and the tasks kept waiting for
  1038.   the semaphore to become free, are chained to the anchor WaitQueue.
  1039.  
  1040.  
  1041. EventType     = RECORD
  1042.                   MemLoc   : Pointer;
  1043.                   OrgValue : Byte;
  1044.                   Task     : TaskNoType;
  1045.                 END;
  1046.  
  1047.   We have already heard about events and what they are for.
  1048.   Here is how they are stored internally.
  1049.   "MemLoc" points to the memory-location to monitor.
  1050.   "OrgValue" contains the original contents of the byte at MemLoc^.
  1051.   "Task" finally is the task-id of the requesting task. It is used
  1052.   to index into the task-table when re-scheduling the task.
  1053.   Right before re-appending the task to its scheduler-queue, the
  1054.   kernel checks whether it is still alive!
  1055.  
  1056.  
  1057. TimerType     = RECORD
  1058.                   MemLoc   : Pointer;
  1059.                   Ticks    : Word;
  1060.                   Task     : TaskNoType;
  1061.                   Next     : TimerPtr;
  1062.                 END;
  1063.  
  1064.   This is how timers are stored. Whenever a timer is created, the
  1065.   necessary memory is allocated on the heap. Than the timer is
  1066.   inserted into the TimerQueue. The single timers contained in this
  1067.   queue are chained through their "Next" pointers. A NIL-pointer
  1068.   terminates the timer-queue.
  1069.   "Ticks" is used to store the number of timer-ticks to wait until
  1070.   the timer expires.
  1071.   "MemLoc" points to a memory location whose content is incremented
  1072.   as soon as the timer expires. Before the kernel actually increments
  1073.   the byte at "MemLoc", it checks whether the task is still alive.
  1074.   If not, no action is taken for not to destroy a memory location
  1075.   that may already belong to another task's private address-space.
  1076.   "Task" again holds the task-id of the requesting process, which is
  1077.   used to index into the task-table.
  1078.  
  1079.  
  1080. PointerType   = RECORD CASE Integer OF
  1081.                   1: (P    : Pointer);
  1082.                   2: (POfs : Word;
  1083.                       PSeg : Word);
  1084.                 END;
  1085.  
  1086.   Split up a pointer in its segment and offset components.
  1087.  
  1088.  
  1089. Internal Constants
  1090.  
  1091. TicksPerSecond = 18;
  1092.  
  1093.   This constant is used to describe the number of timer-ticks that
  1094.   make up a second. You could easily increase the number of
  1095.   task-switches by using the RTC of an AT-computer for example. If
  1096.   after the change this constant reflects the new number of
  1097.   interrupts per second, any program using Seconds() to calculate
  1098.   its sleeping time, will not have to be adapted.
  1099.  
  1100.  
  1101. MaxEvents = 10;
  1102.  
  1103.   "MaxEvents" determines the maximum slots in the event-table. It is
  1104.   recommended to use the smallest possible value to keep the amount
  1105.   of time needed to search this table as small as possible.
  1106.  
  1107.  
  1108. Quantum : Byte = 2;
  1109. MaxDynQuantum : Byte = 10;
  1110.  
  1111.   Simply default values that may be changed by means of the
  1112.   TimeSlice function.
  1113.  
  1114.  
  1115. MaxTasks = 50;
  1116.  
  1117.   This value determines the maximum number of slots in the
  1118.   task-table.
  1119.  
  1120.  
  1121. SafetyMargin = 20;
  1122.  
  1123.   Whenever the kernel checks for stack-overflow conditions, it
  1124.   assumes a margin of "SafetyMargin"-bytes from the highest possible
  1125.   stack-address to be "untouchable" and issues a stack-overflow
  1126.   message if any byte in this area is used by the corresponding task.
  1127.  
  1128.  
  1129. Internal Variables
  1130.  
  1131. TaksTable : ARRAY[1..MaxTasks] OF TaskDescType;
  1132.  
  1133.   The root of all evil - the task-table.
  1134.  
  1135.  
  1136. CurrentTask : TaskPtrType;
  1137.  
  1138.   This pointer is used to hold the address of the task-descriptor
  1139.   belonging to the currently running task. It may only be
  1140.   manipulated by the scheduler.
  1141.  
  1142.  
  1143. TaskQueues ARRAY[Pri_Nice..Pri_Kernel] OF SchedRecType;
  1144.  
  1145.   These are the anchors of the various scheduler-queues.
  1146.  
  1147.  
  1148. SleepQueue : SchedRecType;
  1149.  
  1150.   Here your tasks may sleep for a while....
  1151.  
  1152.  
  1153. EventTable : ARRAY[1..MaxEvents] OF EventType;
  1154.  
  1155.   Whenever an EventWait is issued, a slot of this table is allocated
  1156.   for the requested event. The number of table entries available
  1157.   reflects the maximum possible number of simultaneously active
  1158.   events.
  1159.  
  1160.  
  1161. ActiveEvents : Byte;
  1162.  
  1163.   To be able to skip event-checking, the kernel counts the number of
  1164.   currently active events. Whenever this field contains zero, no
  1165.   check for events is performed.
  1166.  
  1167.  
  1168. TimerQueue : TimerPtr;
  1169.  
  1170.   This is the head of the timer-queue. If the pointer contains NIL,
  1171.   no timers are currently active.
  1172.  
  1173.  
  1174. InDosFlag : PointerType;
  1175. DosCritical : PointerType;
  1176.  
  1177.   DOS uses two memory-locations to store flags that contain a
  1178.   non-zero value whenever DOS is not in a consistent state. The
  1179.   pointers named above are used to hold the addresses of those
  1180.   memory-locations.
  1181.  
  1182.  
  1183. CSDataPtr : ^CSDataType;
  1184.  
  1185.   This pointer holds the address of a small status area located in the
  1186.   code-segment of the assembly language module.
  1187.  
  1188.  
  1189. Reference Section (alphabetically sorted)
  1190.  
  1191.                                                              BindCpu
  1192.  
  1193.  
  1194. Declaration
  1195.  
  1196.    PROCEDURE BindCPU;
  1197.  
  1198. Purpose
  1199.  
  1200.    BindCPU is used to bind the CPU to the currently running process
  1201.    temporarily. No task-switch will be performed an no other
  1202.    scheduler activities take place until the CPU is released by a
  1203.    ReleaseCPU call.
  1204.  
  1205.  
  1206. Example
  1207.  
  1208.       ...
  1209.    BindCPU;                                    {gain CPU exclusivly}
  1210.    GotoXY(1,10);                               {atomic action}
  1211.    Write('*');
  1212.    ReleaseCPU;
  1213.       ...
  1214.  
  1215. Remarks
  1216.  
  1217.    BindCPU blocks the whole kernel! While the CPU is bound, no
  1218.    event-checks, no timer-countdown and no sleep-queue handling are
  1219.    performed.
  1220.    You should only use BindCPU to protect VERY small portions of
  1221.    code. In most cases, the use of semaphores is recommended
  1222.  
  1223. See Also
  1224.  
  1225.    ReleaseCPU
  1226.  
  1227.  
  1228.  
  1229.                                                CancelTimer / CreateSem
  1230.  
  1231.  
  1232. Declaration
  1233.  
  1234.    FUNCTION CancelTimer(MemPtr:Pointer):BOOLEAN;
  1235.  
  1236. Purpose
  1237.  
  1238.    If there exists a timer that will cause the memory location at the
  1239.    address "MemPtr" to become incremented, it will be removed from the
  1240.    system. CancelTimer returns TRUE, if a timer could be found,
  1241.    otherwise FALSE is returned.
  1242.  
  1243. Example
  1244.  
  1245.    VAR Timeout : Boolean;
  1246.        Ok      : Boolean;
  1247.          ...
  1248.    Timeout := False;                           {initialize}
  1249.    IF QueueTimer(@Timeout,Seconds(1))          {create Timer}
  1250.       THEN  BEGIN
  1251.                REPEAT                          {wait}
  1252.                ...                             {some action}
  1253.                UNTIL Timeout OR Ok;
  1254.                IF CancelTimer(@Timeout) THEN;  {Kill Timer}
  1255.                ...                             {some more action}
  1256.             END;
  1257.  
  1258. See Also
  1259.  
  1260.    QueueTimer
  1261.  
  1262.  
  1263. ────────────────────────────────────────────────────────────────────
  1264.  
  1265.  
  1266. Declaration
  1267.  
  1268.    FUNCTION CreateSem(VAR SemPtr:Pointer):SemReturn;
  1269.  
  1270. Purpose
  1271.  
  1272.    CreateSem allocates a semaphore on the heap and returns a pointer
  1273.    that functions as a handle in further system-calls referring to
  1274.    this semaphore. The handle is placed into the variable SemPtr.
  1275.    The return value of this system-function indicates whether the
  1276.    action could be completed successfully.
  1277.  
  1278. Example
  1279.  
  1280.    VAR  Semaphore : Pointer;       {Handle}
  1281.          ...
  1282.    IF CreateSem(Semaphore) <> Sem_Ok
  1283.       THEN Error;
  1284.  
  1285. See Also
  1286.  
  1287.    Typedefinition SemReturn, RemoveSem
  1288.                                                           CreateTask
  1289.  
  1290.  
  1291. Declaration
  1292.  
  1293.    FUNCTION CreateTask(TaskPointer:Pointer;Level:Priority;
  1294.             BytesStack:Word):TaskNoType;
  1295.  
  1296. Purpose
  1297.  
  1298.    This function is used to create a subtask.
  1299.    The address of the procedure to be activated as an independently
  1300.    running task is to be passed in "TaskPointer".
  1301.    "Level" describes the desired priority level.
  1302.    Finally, the parameter "BytesStack" contains the desired size of
  1303.    the task's private stack in bytes.
  1304.  
  1305.    CreateTask returns the task-id or a negative value indicating an
  1306.    error-condition.
  1307.  
  1308. Example
  1309.  
  1310.    PROCEDURE SubTask;
  1311.    {
  1312.      I am a task that beeps every second
  1313.    }
  1314.    BEGIN {SubTask}
  1315.       REPEAT                                   {  Body    ─┐ }
  1316.          Sleep(Seconds(1));                    {           │ }
  1317.          Sound(1000);                          { ACTION!!  │ }
  1318.          Delay(20);                            {    :      │ }
  1319.          NoSound;                              {           │ }
  1320.       UNTIL False;                             {  Body    ─┘ }
  1321.    END;  {SubTask}
  1322.          ...
  1323.          ...
  1324.    BEGIN {Main}
  1325.    IF CreateTask(@SubTask,Pri_User,300) < 0
  1326.       THEN Error;
  1327.          ...
  1328.    END. {Main}
  1329.  
  1330. Remarks
  1331.  
  1332.    With respects to programming, a task is realized as a Pascal
  1333.    procedure without parameters, whose body has to be an infinite
  1334.    loop. You must NEVER leave the body of a task except by executing
  1335.    a Terminate system-call.
  1336.    A task is allowed to have local variable definitions, you will
  1337.    have to take care however, that the private stack is properly
  1338.    dimensioned to be able to hold the local data. You will have to
  1339.    add the amount of memory needed to hold the local variables to
  1340.    the bytes stackspace the task needs at runtime.
  1341.    YOU are responsible for passing a valid stack size!!!! This
  1342.    parameter is not checked!!
  1343.  
  1344. See Also
  1345.  
  1346.    Typedefinitions TaskNoType and Priority, Global Constants,
  1347.    Terminate
  1348.  
  1349.  
  1350.                                                              DoShutDown
  1351.  
  1352.  
  1353.  
  1354. Declaration
  1355.  
  1356.    PROCEDURE DoShutDown;
  1357.  
  1358. Purpose
  1359.  
  1360.    Shutdown the Multi-Tasking Subsystem. All interrupt vectors
  1361.    captured are restored to their original state.
  1362.  
  1363. Remarks
  1364.  
  1365.    This procedure is called internally through the exit-procedure of
  1366.    the subsystem-unit.
  1367.  
  1368.                                                        DumpTaskTable
  1369.  
  1370.  
  1371. Declaration
  1372.  
  1373.    PROCEDURE DumpTaskTable;
  1374.  
  1375. Purpose
  1376.  
  1377.    Display the actual state of all active tasks in the system.
  1378.  
  1379. Example
  1380.  
  1381.    Nr. Status      Priorität  Slice  Delay  Stack free  CPU
  1382.      1     Ready      Pri_Nice     2      0         205  18
  1383.      2   Waiting      Pri_User     3      0          -1  1
  1384.      3   Running    Pri_Kernel    -1      0         221  1
  1385.      4   Waiting      Pri_User     2      0         321  5
  1386.  
  1387. Remarks
  1388.  
  1389.    The current content of your display are overwritten. In case you
  1390.    wish to realize a status-window, you should replace DumpTaskTable
  1391.    with a routine of your own.
  1392.  
  1393. See Also
  1394.  
  1395.    Global Constants StateText, GetTaskState
  1396.                                                   EventWait / GetPID
  1397.  
  1398.  
  1399. Declaration
  1400.  
  1401.    FUNCTION EventWait(MemPtr:Pointer):Boolean;
  1402.  
  1403. Purpose
  1404.  
  1405.    EventWait causes your task to be suspended until the contents of
  1406.    the memory-location pointed at by "MemPtr" changes.
  1407.    If no empty event-table entry can be found, EventWait returns
  1408.    FALSE.
  1409.  
  1410. Example
  1411.  
  1412.    VAR HeadPointer : Word;                     {Bufferpointer   }
  1413.        TailPointer : Word;                     {        "       }
  1414.             ...
  1415.    IF NOT EventWait(@TailPointer)              {wait for a change}
  1416.       THEN Error                               {of the pointer}
  1417.       ELSE DoJob;
  1418.             ...
  1419.  
  1420.  
  1421. ────────────────────────────────────────────────────────────────────
  1422.  
  1423.  
  1424. Declaration
  1425.  
  1426.    FUNCTION GetPID:TaskNoType;
  1427.  
  1428. Purpose
  1429.  
  1430.    By calling GetPID, a task can find out its task-id.
  1431.  
  1432. See Also
  1433.  
  1434.    Typedefinition TaskNoType
  1435.                                            GetTaskState / GetTimBusy
  1436.  
  1437.  
  1438. Declaration
  1439.  
  1440.    FUNCTION GetTaskState(Task:TaskNoType):TaskStatus;
  1441.  
  1442. Purpose
  1443.  
  1444.    GetTaskState returns the current status of the task whose task-id
  1445.    was passed to the function.
  1446.  
  1447. Example
  1448.  
  1449.    This task displays its own state on the screen:
  1450.  
  1451.    Writeln(StateText[GetTaskState(GetPID)]);
  1452.  
  1453. See Also
  1454.  
  1455.    Typedefinitions TaskStatus and TaskNoType, Constant StateText
  1456.  
  1457.  
  1458. ────────────────────────────────────────────────────────────────────
  1459.  
  1460.  
  1461. Declaration
  1462.  
  1463.    FUNCTION GetTimBusy:BPtr;
  1464.  
  1465. Purpose
  1466.  
  1467.    GetTimBusy returns the address of the timer-busy flag which
  1468.    contains a non-zero value whenever the interrupt-handling
  1469.    procedure for the INT 8 is active.
  1470.    Sometimes an external interrupt-handler needs to know, whether
  1471.    the scheduler is currently busy.
  1472.    No system-call must be executed whilst the busy-flag is set.
  1473.  
  1474. See Also
  1475.  
  1476.    Typedefinition BPtr
  1477.  
  1478.                                             QueueTimer / ReadySuspended
  1479.  
  1480.  
  1481. Declaration
  1482.  
  1483.    FUNCTION QueueTimer(MemPtr:Pointer; Counter:Word):BOOLEAN;
  1484.  
  1485. Purpose
  1486.  
  1487.    Creates a timer that expires after "Counter" ticks and increments
  1488.    the contents of the memory-location pointed at by "MemPtr".
  1489.    If there is not enough heap space available to allocate a timer,
  1490.    QueueTimer will return FALSE, otherwise TRUE is returned.
  1491.  
  1492. Example
  1493.  
  1494.    VAR Timeout : Boolean;                      {Timeout marker}
  1495.             ...
  1496.    Timeout := False;                           {initialize    }
  1497.    IF NOT QueueTimer(@Timeout,Seconds(1))      {Timeout after 1 sec}
  1498.       THEN Error;
  1499.             ...
  1500.    IF CancelTimer(@Timeout)                    {Remove Timer     }
  1501.       THEN ;                                   {if still existent}
  1502.             ...
  1503.  
  1504.  See Also
  1505.  
  1506.    CancelTimer
  1507.  
  1508.  
  1509. ────────────────────────────────────────────────────────────────────
  1510.  
  1511.  
  1512. Declaration
  1513.  
  1514.    FUNCTION ReadySuspended(Task:TaskNoType):BOOLEAN;
  1515.  
  1516. Purpose
  1517.  
  1518.    ReadySuspended is used to re-enqueue a task into its
  1519.    scheduler-queue wich has been suspended using the Suspend
  1520.    system-function.
  1521.    If the action could be completed successfully, the value TRUE is
  1522.    returned. A return value of FALSE indicates that the task addressed
  1523.    was not currently suspended.
  1524.  
  1525. See Also
  1526.  
  1527.    Suspend
  1528.                                                                 Receive
  1529.  
  1530.  
  1531. Declaration
  1532.  
  1533.    FUNCTION Receive(FromTask:TaskNoType; MsgBuff:Pointer;
  1534.                     WaitFlag:WaitFlagType):TaskReturn;
  1535.  
  1536. Purpose
  1537.  
  1538.    By executing a Receive system-call, a task can receive a message
  1539.    from another task in the system.
  1540.    The first parameter, "FromTask", indicates from which task the
  1541.    caller would like to receive. If "FromTask" contains "AnyTask",
  1542.    any message is passed through, otherwise only messages sent by
  1543.    the process whose id matches "FromTask" are received.
  1544.    "MsgBuff" points to the start of a message buffer into which the
  1545.    received message is copied. The kernel does not check whether the
  1546.    message received fits into the message buffer - it simply
  1547.    copies!
  1548.    If the caller passes a "WaitFlag" of value "Wait", it will be
  1549.    suspended until a message arrives. Otherwise the kernel
  1550.    immediately returns control to the caller if no message is
  1551.    currently available.
  1552.  
  1553. Example
  1554.  
  1555.    VAR Puffer : String;                        {Message buffer}
  1556.                ...
  1557.    IF Receive(AnyTask,@Puffer,Wait) <> Task_Ok
  1558.       THEN Error
  1559.       ELSE Writeln(Puffer);
  1560.                ...
  1561.  
  1562. Remarks
  1563.  
  1564.    Using the don't care task-id "AnyTask", you could realize a task
  1565.    that spends its life waiting for messages from its environment.
  1566.    If you had to monitor some sensors, for example, you could have a
  1567.    separate task for every sensor to be monitored. Whenever some
  1568.    action has to be taken, the corresponding monitor-task sends a
  1569.    message to a central workhorse that performs the necessary
  1570.    adjustments.
  1571.  
  1572. See Also
  1573.  
  1574.    Constant AnyTask, Typedefinition TaskReturn, Send, ReceivedFrom
  1575.                                            ReceivedFrom / ReleaseCPU
  1576.  
  1577.  
  1578. Declaration
  1579.  
  1580.    FUNCTION ReceivedFrom:TaskNoType;
  1581.  
  1582. Purpose
  1583.  
  1584.    By calling ReceivedFrom, a task can find out, who was the sender
  1585.    of the last message received.
  1586.  
  1587. See Also
  1588.  
  1589.    Typedefinition TaskNoType, Constant AnyTask, Receive
  1590.  
  1591.  
  1592. ────────────────────────────────────────────────────────────────────
  1593.  
  1594.  
  1595. Declaration
  1596.  
  1597.    PROCEDURE ReleaseCPU;
  1598.  
  1599. Purpose
  1600.  
  1601.    Provided you have bound the CPU by executing a BindCPU, you will
  1602.    have to reenable scheduling through execution of a ReleaseCPU.
  1603.  
  1604. Example
  1605.  
  1606.       ...
  1607.    BindCPU;                                    {disable scheduler}
  1608.    GotoXY(1,10);                               {atomic action}
  1609.    Write('*');
  1610.    ReleaseCPU;                                 {...set them free...}
  1611.       ...
  1612.  
  1613. See Also
  1614.  
  1615.    BindCPU;
  1616.                                                    RemoveSem / Sched
  1617.  
  1618.  
  1619. Declaration
  1620.  
  1621.    FUNCTION RemoveSem(VAR SemPtr:Pointer):SemReturn;
  1622.  
  1623. Purpose
  1624.  
  1625.    RemoveSem physically removes a semaphore. The formerly occupied
  1626.    heap space is returned to the memory pool.
  1627.    "SemPtr" must be a valid semaphore-handle as returned by the
  1628.    CreateSem system-call.
  1629.    RemoveSem returns a value of type SemReturn that indicates
  1630.    success or failure.
  1631.  
  1632. Example
  1633.  
  1634.    VAR  Semaphore : Pointer;                   {Handle}
  1635.                ...
  1636.    IF CreateSem(Semaphore) <> Sem_Ok
  1637.       THEN Error;
  1638.             ...
  1639.    IF RemoveSem(Semaphore) <> Sem_Ok
  1640.       THEN Error;
  1641.             ...
  1642.  
  1643. Remarks
  1644.  
  1645.    The system mostly is not able to prove whether a valid handle is
  1646.    supplied. It is your job to take care of this.
  1647.    You cannot remove a semaphore, whose task-queue currently is not
  1648.    empty.
  1649.  
  1650. See Also
  1651.  
  1652.    Typedefinition SemReturn, CreateSem
  1653.  
  1654.  
  1655. ────────────────────────────────────────────────────────────────────
  1656.  
  1657.  
  1658. Declaration
  1659.  
  1660.    PROCEDURE Sched;
  1661.  
  1662. Purpose
  1663.  
  1664.    Initiate a task-switch, give up the current timeslice.
  1665.    "Sched" is used internally by many system-functions. If you want
  1666.    to have your task give up its timeslice, you should better use
  1667.    Sleep(1).
  1668.  
  1669.                                                   Seconds / SemClear
  1670.  
  1671.  
  1672. Declaration
  1673.  
  1674.    FUNCTION Seconds(Sec:Word):LongInt;
  1675.  
  1676. Purpose
  1677.  
  1678.    "Seconds" returns the number of timer-ticks that make up a second.
  1679.    It is recommended that you use this function whenever you need to
  1680.    specify a special amount of time. Use of Seconds makes your
  1681.    application independent of changes to the task-switch rate.
  1682.  
  1683. Example
  1684.  
  1685.    IF QueueTimer(@Timeout,Seconds(1) SHR 1)    { timout after   }
  1686.       THEN;                                    { half a second  }
  1687.  
  1688. See Also
  1689.  
  1690.    QueueTimer, CancelTimer
  1691.  
  1692.  
  1693. ────────────────────────────────────────────────────────────────────
  1694.  
  1695.  
  1696.  
  1697. Declaration
  1698.  
  1699.    PROCEDURE SemClear(SemPtr:Pointer);
  1700.  
  1701. Purpose
  1702.  
  1703.    Reset a semaphore's signal-count to zero.
  1704.  
  1705. Example
  1706.  
  1707.    VAR  Semaphore : Pointer;                   {Handle}
  1708.                ...
  1709.    IF CreateSem(Semaphore) <> Sem_Ok
  1710.       THEN Error
  1711.       ELSE SemClear(Semaphore);
  1712.  
  1713. Remarks
  1714.  
  1715.    Internally SemClear is translated into SemSet(Semaphore,0).
  1716.  
  1717. See Also
  1718.  
  1719.    SemSignal, SemWait, SemClearWait, SemSet
  1720.                                                         SemClearWait
  1721.  
  1722.  
  1723. Declaration
  1724.  
  1725.    PROCEDURE SemClearWait(SemPtr:Pointer);
  1726.  
  1727. Purpose
  1728.  
  1729.    Reset a semaphore's signal-count to zero and wait until another
  1730.    task initiates a SemSignal system-call for this semaphore.
  1731.    In contrast to SemWait, SemClearWait ALWAYS leads to the calling
  1732.    task beeing suspended.
  1733.  
  1734. Example
  1735.  
  1736.    VAR Ready : Pointer;                        {Semaphore      }
  1737.          ...
  1738.    PROCEDURE SubTask;
  1739.    {
  1740.       I am a task that needs to save the values of some global
  1741.       variables during initialisation. The main program, however,
  1742.       changes these variables during the process of its execution.
  1743.       Therefore, the main program has to wait until I indicate that I
  1744.       have transferred the values I need to my local data area.
  1745.    }
  1746.    BEGIN {SubTask}
  1747.          ...                                   {initialize     }
  1748.       SemSignal(Ready);                        {O.K. I'm ready }
  1749.       REPEAT
  1750.          ...                                   {Body           }
  1751.       UNTIL False;
  1752.    END;  {SubTask}
  1753.          ...
  1754.          ...
  1755.    BEGIN {Main}
  1756.          ...
  1757.    IF CreateSem(Ready) <> Sem_Ok               {Create a semaphore}
  1758.       THEN Error;
  1759.    IF CreateTask(@SubTask,Pri_User,300) < 0    {Create a task     }
  1760.       THEN Error
  1761.       ELSE SemClearWait(Ready);                {wait for O.K.    }
  1762.          ...
  1763.    END. {Main}
  1764.  
  1765. See Also
  1766.  
  1767.    SemClear, SemSet, SemSignal, SemWait
  1768.  
  1769.                                               SemCut / SemGetSignals
  1770.  
  1771.  
  1772. Declaration
  1773.  
  1774.    FUNCTION SemCut(SemPtr:Pointer;Task:TaskNoType):BOOLEAN;
  1775.  
  1776. Purpose
  1777.  
  1778.    If the task whose id is passed as "Task" currently is waiting in
  1779.    "SemPtr"'s task-queue, it is removed from this queue.
  1780.    SemCut returns TRUE, if the action could be completed successfully.
  1781.  
  1782. Remarks
  1783.  
  1784.    SemCut is a somewhat brutal function! It forces an innatural
  1785.    action and was added for a special application purpose in MtPopUp.
  1786.    PLEASE do use it with care!! - Better keep away from it.
  1787.  
  1788. See Also
  1789.  
  1790.    SemPaste
  1791.  
  1792.  
  1793. ────────────────────────────────────────────────────────────────────
  1794.  
  1795.  
  1796. Declaration
  1797.  
  1798.    FUNCTION SemGetSignals(SemPtr:Pointer):Word;
  1799.  
  1800. Purpose
  1801.  
  1802.    Returns the signal-count of the semaphore "SemPtr".
  1803.  
  1804. Example
  1805.  
  1806.    FUNCTION SemBusy(S:Pointer):BOOLEAN;
  1807.    {
  1808.       This function checks whether a semaphore is currently busy.
  1809.    }
  1810.    BEGIN {SemBusy}
  1811.       SemBusy := (SemGetSignals(S) = 0);       {Signal-Count = 0}
  1812.    END;  {SemBusy}
  1813.  
  1814. See Also
  1815.  
  1816.    SemSignal, SemWait, SemClear, SemClearWait, SemSet
  1817.                                                    SemPaste / SemSet
  1818.  
  1819.  
  1820. Declaration
  1821.  
  1822.    PROCEDURE SemPaste(SemPtr:Pointer; Task:TaskNoType);
  1823.  
  1824. Purpose
  1825.  
  1826.    SemPaste unconditionally appends "Task" to the task-queue of the
  1827.    semaphore referred to by "SemPtr". No validity checks are performed!
  1828.  
  1829. Remarks
  1830.  
  1831.  
  1832.    SemCut is a somewhat brutal function! It forces an innatural
  1833.    action and was added for a special application purpose in MtPopUp.
  1834.    PLEASE do use it with care!! - Better keep away from it.
  1835.  
  1836.  
  1837. ────────────────────────────────────────────────────────────────────
  1838.  
  1839.  
  1840. Declaration
  1841.  
  1842.    PROCEDURE SemSet(SemPtr:Pointer; Count:Word);
  1843.  
  1844. Purpose
  1845.  
  1846.    SemSet sets the signal-count of a semaphore to a definite value.
  1847.    This action does not have any influence on tasks that might be
  1848.    waiting in the task-queue belonging to this semaphore.
  1849.  
  1850. Example
  1851.  
  1852.    CONST Elements = 100;
  1853.    VAR   Buffer   : ARRAY[1..Elements] OF Byte; {Buffer}
  1854.          Full     : Pointer;                   {No. of used slots}
  1855.          Empty    : Pointer;                   {No. of empty slots}
  1856.          ...
  1857.    BEGIN {Main}
  1858.          ...
  1859.       IF CreateSem(Full) = Sem_Ok
  1860.          THEN SemClear(Full);                  {no slot used}
  1861.       IF CreateSem(Empty) = Sem_Ok
  1862.          THEN SemSet(Empty,Elements);          {all slots free}
  1863.          ...
  1864.    END. {Main}
  1865.  
  1866. See Also
  1867.  
  1868.    SemSignal, SemWait, SemClear, SemClearWait
  1869.                                             SemSignal / SemSoWaiting
  1870.  
  1871.  
  1872. Declaration
  1873.  
  1874.    PROCEDURE SemSignal(SemPtr:Pointer);
  1875.  
  1876. Purpose
  1877.  
  1878.    If the task-queue of the semaphore referred to by "SemPtr" is
  1879.    currently empty, the signal-count will be incremented. Otherwise
  1880.    the first task waiting is re-scheduled and the signal-count remains
  1881.    unchanged.
  1882.  
  1883. Example
  1884.  
  1885.    VAR Critical: Pointer;
  1886.          ...
  1887.    IF SemCreate(Critical) <> Sem_Ok
  1888.    THEN Error;
  1889.          ...
  1890.    SemWait(Critical);
  1891.          ...
  1892.    {critical section}
  1893.          ...
  1894.    SemSignal(Critical);
  1895.          ...
  1896.  
  1897. See Also
  1898.  
  1899.    SemWait, SemClearWait, SemClear, SemSet
  1900.  
  1901.  
  1902. ────────────────────────────────────────────────────────────────────
  1903.  
  1904.  
  1905. Declaration
  1906.  
  1907.    FUNCTION SemSoWaiting(SemPtr:Pointer):BOOLEAN;
  1908.  
  1909. Purpose
  1910.  
  1911.    SemSoWaiting returns TRUE, if there are tasks waiting for
  1912.    "SemPtr" to become available.
  1913.  
  1914. See Also
  1915.  
  1916.    SemWait, SemSignal, SemClear, SemClearWait, SemSet, SemGetSignals
  1917.                                                       SemWait / Send
  1918.  
  1919.  
  1920. Declaration
  1921.  
  1922.    PROCEDURE SemWait(SemPtr:Pointer);
  1923.  
  1924. Purpose
  1925.  
  1926.    If a semaphore's signal count currently is greater than zero, it
  1927.    will be decremented. Otherwise the caller is suspended and
  1928.    appended to the task-queue of this semaphore.
  1929.    It remains suspended until another task issues a SemSignal for
  1930.    the semaphore it is waiting for.
  1931.  
  1932. Example
  1933.  
  1934.    VAR Critical: Pointer;
  1935.          ...
  1936.    IF SemCreate(Critical) <> Sem_Ok
  1937.    THEN Error;
  1938.          ...
  1939.    SemWait(Critical);
  1940.          ...
  1941.    {critical section}
  1942.          ...
  1943.    SemSignal(Critical);
  1944.          ...
  1945.  
  1946. See Also
  1947.  
  1948.    SemSignal, SemClearWait, SemClear, SemSet
  1949.  
  1950.  
  1951. ────────────────────────────────────────────────────────────────────
  1952.  
  1953.  
  1954. Declaration
  1955.  
  1956.    FUNCTION Send(ToTask:TaskNoType; Msg:Pointer; MsgSize:WORD;
  1957.                  WaitFlag:WaitFlagType):TaskReturn;
  1958.  
  1959. Purpose
  1960.  
  1961.    The system-function "Send" lets a task send a message to another
  1962.    process in the system.
  1963.    The parameter "ToTask" contains the task-id of the receiver. This
  1964.    must be the task-id of a currently active task. You must not use
  1965.    the constant "AnyTask" in this context.
  1966.    "Msg" points to the start of the message-buffer whose size is
  1967.    passed in "MsgSize".
  1968.    Finally a wait-flag has to be supplied, that indicates whether
  1969.    the sender wishes to wait until the receiver is willing to
  1970.    receive the message. If you set "WaitFlag" to "NoWait", the
  1971.    kernel will return control to your task immedeately if the
  1972.    receiver is not waiting for a message.
  1973.                                                        Send / Sleep
  1974.  
  1975.  
  1976. Example
  1977.  
  1978.    VAR Puffer : String;
  1979.          ...
  1980.    IF Receive(AnyTask,@Puffer,Wait) <> Task_Ok
  1981.       THEN Error
  1982.       ELSE Writeln(Puffer);
  1983.          ...
  1984.  
  1985. Remarks
  1986.  
  1987.    The message is physically copied to the message buffer of the
  1988.    receiving process. This is not the best solution from the point
  1989.    of view of performance, but is provided for the highest possible
  1990.    independence of sender and receiver. They could theoretically be
  1991.    located on different machines in a networking environment. If
  1992.    you better like a pointer to a message to be passed, make your
  1993.    pointer the message.
  1994.  
  1995. See Also
  1996.  
  1997.    Typedefinition TaskReturn, Receive
  1998.  
  1999.  
  2000. ────────────────────────────────────────────────────────────────────
  2001.  
  2002.  
  2003. Declaration
  2004.  
  2005.    PROCEDURE Sleep(Ticks:LongInt);
  2006.  
  2007. Purpose
  2008.  
  2009.    "Sleep" lets your task suspend itself for a certain number of
  2010.    timer-ticks.
  2011.  
  2012. Example
  2013.  
  2014.    Writeln('I''m going to sleep for 3 seconds!');
  2015.    Sleep(Seconds(3));
  2016.    Writeln('Hi, here I am again!');
  2017.  
  2018. See Also
  2019.  
  2020.    Seconds
  2021.                                                              SetPri
  2022.  
  2023. Declaration
  2024.  
  2025.    PROCEDURE SetPri(Pri:Priority);
  2026.  
  2027. Purpose
  2028.  
  2029.   "SetPri" lets a Task change its priority at runtime.
  2030.  
  2031. Remarks
  2032.  
  2033.   The new priority will come into effect at the end of
  2034.   its current quantum. Therefore the task will remain in
  2035.   control of the CPU, although it might have lowered its
  2036.   priority.
  2037.  
  2038.                                                 Suspend / Terminate
  2039.  
  2040.  
  2041. Declaration
  2042.  
  2043.    FUNCTION Suspend(Task:TaskNoType):BOOLEAN;
  2044.  
  2045. Purpose
  2046.  
  2047.    Brutally suspend a task which is currently computable.
  2048.    "Task" contains the id of the task to be suspended.
  2049.    A task that has been suspended by a Suspend system-call can ONLY
  2050.    be reactivated by a ReadySuspended system-call.
  2051.    If the task addressed was not computable, Suspend would return
  2052.    FALSE to the caller. Otherwise TRUE is returned.
  2053.  
  2054. See Also
  2055.  
  2056.    Typedefinition TaskNoType, ReadySuspended
  2057.  
  2058.  
  2059. ────────────────────────────────────────────────────────────────────
  2060.  
  2061.  
  2062. Declaration
  2063.  
  2064.    PROCEDURE Terminate;
  2065.  
  2066. Purpose
  2067.  
  2068.    A task may terminate itself by executing a "Terminate"-system-call.
  2069.    The task-descriptor is marked available and the private stack is
  2070.    returned to the memory pool.
  2071.  
  2072. See Also
  2073.  
  2074.    CreateTask
  2075.  
  2076.                                                            TimeSlice
  2077.  
  2078.  
  2079. Declaration
  2080.  
  2081.    PROCEDURE TimeSlice(Slice, DynQ:Byte);
  2082.  
  2083. Purpose
  2084.  
  2085.    The width of a timeslice and the +/- maximum number of ticks to
  2086.    credit/debit during dynamic scheduling, may be changed by issuing
  2087.    a "TimeSlice"-system-call.
  2088.    "Slice" describes the size of a timeslice in timer-ticks;
  2089.    "DynQ" contains the number of ticks to credit/debit during
  2090.    dynamic scheduling.
  2091.    "Slice" defaults to 2; "DynQ" defaults to 10.
  2092.  
  2093.  
  2094. History
  2095.  
  2096. Version 1.30 / November 1988
  2097.  
  2098. - Task queues are now realized as doubled chained lists; this
  2099.   increases speed when removing list elements
  2100. - Tasks beeing awakened by an event or returning from a message
  2101.   passing operation, are now put to the front of their run level
  2102.   task queue. Thus they will be selected by the scheduler earlier.
  2103. - A bug in the delta computation of QueueTimer has been corrected
  2104. - The scheduler stack is no more located in the data segment, but
  2105.   has been moved to the heap
  2106. - The formerly local data type "PointerType" has been made public
  2107. - The assembly language module has become slightly optimized
  2108. - A new procedure "SetPri" has been added
  2109.